﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

using SlimDX;
using SlimDX.Direct3D9;

using Microsoft.Research.DynamicDataDisplay.Charts;
using Microsoft.Research.DynamicDataDisplay.Charts.Legend_items;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using Microsoft.Research.DynamicDataDisplay.Filters;

namespace Microsoft.Research.DynamicDataDisplay.DirectX2D
{
    public class DirectXLineGraph : DirectXPointsGraphBase, IOneDimensionalChart
    {
        private FakePointList filteredPoints = null;

        public FakePointList FilteredPoints
        {
            get { return filteredPoints; }
            set { filteredPoints = value; }
        }
		
        static DirectXLineGraph()
        {
            Type thisType = typeof(DirectXLineGraph);

            NewLegend.DescriptionProperty.OverrideMetadata(thisType, new FrameworkPropertyMetadata("DirectXLineGraph"));
            NewLegend.LegendItemsBuilderProperty.OverrideMetadata(thisType, new FrameworkPropertyMetadata(new LegendItemsBuilder(DefaultLegendItemsBuilder)));
        }

        private Camera camera = new Camera();

        private readonly FilterCollection filters = new FilterCollection();

        private Color4 _lineColor;
        private SlimDX.Direct3D9.Line _line;

        public DirectXLineGraph()
        {
            Legend.SetVisibleInLegend(this, true);
            ManualTranslate = true;

            filters.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(filters_CollectionChanged);

            camera.FieldOfView = (float)(Math.PI / 4);
            camera.NearPlane = 0.0f;
            camera.FarPlane = 40.0f;
            //camera.Location = new Vector3(0.0f, 7.0f, 20.0f);
            camera.Location = new Vector3(0.0f, 0.0f, 1.0f);
            camera.Target = Vector3.Zero;                        
            
        }

        void filters_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            filteredPoints = null;
            Update();
        }

        private static IEnumerable<FrameworkElement> DefaultLegendItemsBuilder(IPlotterElement plotterElement)
        {
            DirectXLineGraph lineGraph = (DirectXLineGraph)plotterElement;

            System.Windows.Shapes.Line line = new System.Windows.Shapes.Line { X1 = 0, Y1 = 10, X2 = 20, Y2 = 0, Stretch = Stretch.Fill, DataContext = lineGraph };
            line.SetBinding(System.Windows.Shapes.Line.StrokeProperty, "Stroke");
            line.SetBinding(System.Windows.Shapes.Line.StrokeThicknessProperty, "StrokeThickness");
            NewLegend.SetVisualContent(lineGraph, line);

            var legendItem = LegendItemsHelper.BuildDefaultLegendItem(lineGraph);
            yield return legendItem;
        }

        short[] _lineListIndices = new short[1];

        private void ResetIndices(int length, int loopCount)
        {
            if (_lineListIndices.Length != length)
            {
                Array.Resize(ref _lineListIndices, length);

                for (int i = 0; i < loopCount; i++)
                {
                    _lineListIndices[i * 2] = (short)(i);
                    _lineListIndices[(i * 2) + 1] = (short)(i + 1);
                }
            }
        }

#if DEBUG
        Stopwatch _stopWatch = new Stopwatch();
#endif

        protected override void OnDirectXRender()
        {
            var device = Device;

            //Color color = (Stroke as SolidColorBrush).Color;
            Color4 color = new Color4((Stroke as SolidColorBrush).Color.ToArgb());
            //var pointlist = DataSource.GetPoints();

            if (_line == null)
            {
                ResetResources();
            }
            
            //foreach (object graph in plotter.Children)
            //{
            //    if (graph is DirectXPointsGraphBase)
            //    {
            //        DirectX2D.DirectXPointsGraphBase graphBase = (DirectX2D.DirectXPointsGraphBase)graph;
            //        pointlist =  graphBase.VisiblePoints;
            //    }
            //}

            //var points = pointlist.ToArray();
            var points = filteredPoints.ToArray<Point>();

            //var pointList = new VertexPosition3ColorPointSize[points.Length];
            var pointList = new Vector2[points.Length];


            //for (int i = 0; i < points.Length; i++)
            //{
             
            //    Point convertedPoint = points[i].DataToScreen((plotter as Plotter2D).Viewport.Transform);
            //    //Point convertedPoint = points[i];

            //    pointList[i] = new VertexPosition3ColorPointSize
            //    {
            //        Position = new Vector3((float)convertedPoint.X, (float)convertedPoint.Y, 0.0f), // old value = 0.5f
            //        Color = color.ToArgb(),
            //        PointSize = 0.1f
            //    };
            //}

            for (int i = 0; i < points.Length; i++)
            {

                Point convertedPoint = points[i].DataToScreen((plotter as Plotter2D).Viewport.Transform);
                //Point convertedPoint = points[i];

                pointList[i] = new Vector2
                {
                    //Position = new Vector3((float)convertedPoint.X, (float)convertedPoint.Y, 0.0f), // old value = 0.5f
                    //Color = color.ToArgb(),
                    //PointSize = 0.1f
                    X = (float)convertedPoint.X,
                    Y = (float)convertedPoint.Y
                };
            }

            

            //ResetIndices((points.Length * 2) - 2, points.Length - 1);

            Device.SetTransform(TransformState.World, SlimDX.Matrix.Translation(0, 0, 0));
            Device.SetTransform(TransformState.View, camera.ViewMatrix);
            Device.SetTransform(TransformState.Projection, camera.ProjectionMatrix);            
            //device.SetRenderState(SlimDX.Direct3D9.RenderState.AntialiasedLineEnable, true);
            //device.SetRenderState(SlimDX.Direct3D9.RenderState.MultisampleAntialias, true);
            //device.SetRenderState(SlimDX.Direct3D9.RenderState., true);
            device.VertexFormat = VertexFormat.Diffuse | VertexFormat.PositionRhw;
            
            //device.DrawIndexedUserPrimitives<short, VertexPosition3ColorPointSize>(PrimitiveType.LineList, 0, 
            //    points.Length, points.Length - 1, _lineListIndices, Format.Index16, pointList, 
            //    VertexPosition3ColorPointSize.SizeInBytes);

            //_line.Draw(pointList, new Color4(color.ToArgb()));
            _line.Draw(pointList, color);
        }

        protected override void ReleaseResources()
        {
            if (_line != null)
                _line.Dispose();
        }

        protected override void ResetResources()
        {
            ReleaseResources();

            _line = new SlimDX.Direct3D9.Line(Device);
            _line.Antialias = true;
            _line.Width = (float)StrokeThickness;
        }

        protected override void UpdateCore()
        {
            
            if (DataSource == null) return;
            if (Plotter == null) return;
            Viewport2D viewport = ((Plotter2D)Plotter).Viewport;
            Rect output = Viewport.Output;
            var transform = GetTransform();

            List<Point> viewportPoints = GetPoints().DataToViewport(transform).ToList();
            List<Point> screenPts = GetPoints().DataToScreenAsList(transform).ToList();            
            List<Point> filterScreenPoints = FilterPoints(screenPts);

            // Now convert filterScreenPoints to viewport points
            List<Point> filteredViewPortPoints = filterScreenPoints.ScreenToViewport(transform).ToList();
            
            
            // Now filter the viewport points.
            this.filteredPoints = new FakePointList(filteredViewPortPoints, Viewport.Output.ScreenToViewport(transform).XMin, Viewport.Output.ScreenToViewport(transform).XMax);


            #region commented code

            //if (filteredPoints == null || !(transform.DataTransform is IdentityTransform))
            //{
            //    IEnumerable<Point> points = GetPoints();

            //    var bounds = BoundsHelper.GetViewportBounds(points, transform.DataTransform);
            //    Viewport2D.SetContentBounds(this, bounds);

            //    // getting new value of transform as it could change after calculating and setting content bounds.
            //    transform = GetTransform();
            //    List<Point> transformedPoints = transform.DataToScreenAsList(points);

            //    // Analysis and filtering of unnecessary points
            //    filteredPoints = new FakePointList(FilterPoints(transformedPoints),
            //        output.Left, output.Right);

            //    if (ProvideVisiblePoints)
            //    {
            //        List<Point> viewportPointsList = new List<Point>(transformedPoints.Count);
            //        if (transform.DataTransform is IdentityTransform)
            //        {
            //            viewportPointsList.AddRange(points);
            //        }
            //        else
            //        {
            //            var viewportPoints = points.DataToViewport(transform.DataTransform);
            //            viewportPointsList.AddRange(viewportPoints);
            //        }

            //        SetVisiblePoints(this, new ReadOnlyCollection<Point>(viewportPointsList));
            //    }

            //    Offset = new Vector();
            //}
            //else
            //{
            //    double left = output.Left;
            //    double right = output.Right;
            //    double shift = Offset.X;
            //    left -= shift;
            //    right -= shift;

            //    filteredPoints.SetXBorders(left, right);
            //}
            #endregion
        }

        private List<Point> FilterPoints(List<Point> points)
        {
            
            var filteredPoints = filters.Filter(points, Viewport.Output);

            return filteredPoints;
        }
        /// <summary>Provides access to filters collection</summary>
        public FilterCollection Filters
        {
            get { return filters; }
        }

        #region Pen

        /// <summary>
        /// Gets or sets the brush, using which polyline is plotted.
        /// </summary>
        /// <value>The line brush.</value>
        public Brush Stroke
        {
            get { return LinePen.Brush; }
            set
            {
                if (LinePen.Brush != value)
                {
                    if (!LinePen.IsSealed)
                    {
                        LinePen.Brush = value;
                        InvalidateVisual();
                    }
                    else
                    {
                        Pen pen = LinePen.Clone();
                        pen.Brush = value;
                        LinePen = pen;
                    }

                    RaisePropertyChanged("Stroke");
                }
            }
        }

        /// <summary>
        /// Gets or sets the line thickness.
        /// </summary>
        /// <value>The line thickness.</value>
        public double StrokeThickness
        {
            get { return LinePen.Thickness; }
            set
            {
                if (LinePen.Thickness != value)
                {
                    if (!LinePen.IsSealed)
                    {
                        LinePen.Thickness = value; InvalidateVisual();
                    }
                    else
                    {
                        Pen pen = LinePen.Clone();
                        pen.Thickness = value;
                        LinePen = pen;
                    }

                    RaisePropertyChanged("StrokeThickness");
                }
            }
        }

        /// <summary>
        /// Gets or sets the line pen.
        /// </summary>
        /// <value>The line pen.</value>
        //[NotNull]
        public Pen LinePen
        {
            get { return (Pen)GetValue(LinePenProperty); }
            set { SetValue(LinePenProperty, value); }
        }

        public static readonly DependencyProperty LinePenProperty =
            DependencyProperty.Register(
            "LinePen",
            typeof(Pen),
            typeof(DirectXLineGraph),
            new FrameworkPropertyMetadata(
                new Pen(Brushes.Blue, 1),
                FrameworkPropertyMetadataOptions.AffectsRender
                ),
            OnValidatePen);

        private static bool OnValidatePen(object value)
        {
            return value != null;
        }

        #endregion

    }
}
